class EventEmitter {
  // 存储事件和事件回调函数容器
  /*
    对象结构：
      {
        eventName1: [cb1, cb2, cb3, ...],
        eventName2: [cb1, cb2, cb3, ...],
      }

    数据结构: 查找很麻烦
      [
        ['eventName', [cb1, cb2, cb3, ...]]
      ]
  */
  _callbacks = {};

  /**
   * 绑定事件
   * @param {*} eventName 事件名
   * @param {*} cb 事件回调函数
   */
  on(eventName, cb) {
    if (cb && typeof cb !== "function") {
      console.error(`cb必须是一个函数`);
      return;
    }
    // 将事件和回调函数存储到_callbacks中
    if (this._callbacks[eventName]) {
      this._callbacks[eventName].push(cb);
    } else {
      this._callbacks[eventName] = [cb];
    }
  }

  /**
   * 绑定一次性事件
   * @param {*} eventName 事件名
   * @param {*} cb 事件回调函数
   */
  once(eventName, cb) {
    if (cb && typeof cb !== "function") {
      console.error(`cb必须是一个函数`);
      return;
    }
    const newCb = (...args) => {
      this.off(eventName, newCb);
      cb(...args);
    };
    this.on(eventName, newCb);
  }

  /**
   * 解绑事件
   * @param {*} eventName 事件名
   * @param {*} cb 事件回调函数
   *
   * @example off() 解绑所有事件和所有回调
   * @example off(eventName) 解绑某个事件的所有回调
   * @example off(eventName, cb) 解绑某个事件的某个回调
   */
  off(eventName, cb) {
    if (eventName && cb) {
      if (typeof cb !== "function") {
        console.error(`cb必须是一个函数`);
        return;
      }
      // 解绑某个事件的某个回调
      this._callbacks[eventName] = this._callbacks[eventName].filter(
        (eventCb) => eventCb !== cb
      );
    } else if (eventName) {
      // 解绑某个事件的所有回调
      this._callbacks[eventName] = [];
    } else {
      // 解绑所有事件和所有回调
      this._callbacks = {};
    }
  }

  /**
   * 触发事件
   * @param {*} eventName 事件名
   * @param  {...any} args 事件回调函数的参数
   */
  emit(eventName, ...args) {
    const callbacks = this._callbacks[eventName];
    if (!callbacks) {
      console.error(`找不到这个 ${eventName} 事件`);
      return;
    }
    callbacks.forEach((cb) => cb.apply(this, args));
  }
}

const emitter = new EventEmitter();

export default emitter;
